﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;

namespace Klinkby.Scheduler
{
    /// <summary>
    /// Runs scheduled tasks on an asp.net webserver
    /// </summary>
    /// <example>Add a<c><![CDATA[Application["scheduler"] = new Scheduler(...);]]></c>statement to the Application_Start event in global.asax</example>
    [Serializable]
    public class Scheduler : MarshalByRefObject
    {
        readonly Cache _cache; // HttpContext isn't available from the callback

        public Scheduler(IScheduledTask[] tasks)
	    {
            if (tasks == null)
                throw new ArgumentNullException("tasks");
            if (HttpContext.Current == null)
                throw new InvalidOperationException("No HttpContext available");
            _cache = HttpContext.Current.Cache;
            foreach(var task in tasks)
                ScheduleTask(task);
	    }

        void ScheduleTask(IScheduledTask task)
        {
            if (task == null)
                throw new ArgumentNullException("task");
            _cache.Add(
                    "scheduler-" + task.GetType().Name + '-' + task.GetHashCode().ToString("x"),
                    task,
                    null,
                    task.Interval.Next,
                    Cache.NoSlidingExpiration,
                    CacheItemPriority.NotRemovable,
                    OnCacheItemRemoved);
        }

        void OnCacheItemRemoved(string key, object value, CacheItemRemovedReason reason)
        {
            if (reason == CacheItemRemovedReason.Removed)
                return; // skip, happens when application shuts down
            var task = (IScheduledTask)value;
            try
            {
                Raise(TaskExecuting, new EventArgs<IScheduledTask>(task));
                Execute(task);
                Raise(TaskExecuted, new EventArgs<TaskCompletedEventArgs>(
                    new TaskCompletedEventArgs(task, null)));
            }
            catch (Exception e)
            {
                Raise(TaskExecuted, new EventArgs<TaskCompletedEventArgs>(
                    new TaskCompletedEventArgs(task, e)));
            }
            finally
            {
                ScheduleTask(task);
            }
        }

        protected virtual void Execute(IScheduledTask task)
        {
            task.Execute();
        }

        void Raise<T>(EventHandler<T> handler, T e)
            where T: EventArgs
        {
            if (handler != null)
                handler(this, e);
        }

        public event EventHandler<EventArgs<IScheduledTask>> TaskExecuting;
        public event EventHandler<EventArgs<TaskCompletedEventArgs>> TaskExecuted;
    }
}
